﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Data;
using System.Dynamic;
using System.Linq;
using System.Text;
using T.ServiceComponent.Log.Client;
using T.ServiceComponent.Log.SPI;

namespace T.FAS.Runtime.Base.Entity
{
    public class TRecord : DynamicObject, ICloneable
    {
        public readonly string TRecordID = Guid.NewGuid().ToString();
        public const string ChangeSetPropertyName = "_$changeset";
        public const string ChangeTypePropertyName = "_$changetype";
        Dictionary<string, object> _properties;
        DataRow row;
        public const int RecursiveDepth = 30;
        public int Count
        {
            get
            {
                return _properties.Count;
            }
        }
        public string PKColumnName { get; set; }
        /// <summary>
        /// 时间戳字段名称，用于准确获取当前CDO的最后修改时间字段名称，生成变更日志创建时间更准确
        /// </summary>
        public string TimestampColumnName { get; set; }
        public List<string> StateColumnNames { get; set; }
        public List<string> SDOColumnNames { get; set; }
        private Dictionary<string, string> _allColumnInfos = null;
        public Dictionary<string, string> AllColumnInfos
        {
            get
            {
                if (_allColumnInfos == null)
                    _allColumnInfos = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                return _allColumnInfos;
            }
            set
            {
                _allColumnInfos = value;
            }
        }
        //TODO 这个pkColumnValue初始值设置为“”, 不一定正确？
        private string pkColumnValue = "";
        public string PKColumnValue
        {
            get
            {
                if (!string.IsNullOrWhiteSpace(PKColumnName))
                {
                    var pkValue = GetValue(PKColumnName);
                    if (pkValue != null)
                    {
                        pkColumnValue = Convert.ToString(pkValue);
                        return pkColumnValue;
                    }

                    return pkColumnValue;
                }
                else
                {
                    return pkColumnValue;
                }
            }
            set
            {
                pkColumnValue = value;
            }
        }

        /// <summary>
        /// 索引器
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public object this[string name]
        {
            get
            {
                if (_properties != null && _properties.ContainsKey(name))
                {
                    return _properties[name];
                }
                return null;
            }
            set
            {
                SetValue(name, value);
            }
        }

        #region 构造函数
        public TRecord()
        {
            _properties = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            ChangeSet = new TChangeSet();
        }

        public TRecord(Dictionary<string, object> properties) : this()
        {
            if (properties != null)
                _properties = new Dictionary<string, object>(properties, StringComparer.OrdinalIgnoreCase);
        }
        public TRecord(Dictionary<string, object> properties, string pkColumnName) : this()
        {
            if (properties != null)
                _properties = new Dictionary<string, object>(properties, StringComparer.OrdinalIgnoreCase);
            PKColumnName = pkColumnName;
        }

        public TRecord(DataRow row) : this()
        {
            this.row = row;
        }

        #endregion

        #region 重写方法
        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return _properties.Keys;
        }
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            string name = binder.Name;
            UpdateChangeSet(name, value);
            if (_properties.ContainsKey(name))
            {
                _properties[name] = value;
                return true;
            }
            else
            {
                _properties.Add(name, value);
                return true;
            }
        }
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            string name = binder.Name;
            if (_properties.ContainsKey(name))
            {
                result = _properties[name];
                return true;
            }
            else
            {
                result = null;
                return false;
            }
        }

        #endregion

        #region Methods
        public void CopyTRecord(TRecord record)
        {
            this._properties = record._properties;
        }

        /// <summary>
        /// 根据JObject生成TRecord
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static TRecord BuildRecord(JObject data)
        {
            if (data == null)
                return null;
            Dictionary<string, object> pairs = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            TChangeSet changeSet = default;
            foreach (var item in data.Properties())
            {
                //特殊处理TChangeSet结构
                if (string.Equals(item.Name, ChangeSetPropertyName))
                {
                    if (item.Value != default(JToken) && item.Value.Type == JTokenType.Object)
                    {
                        JObject changeSetObj = item.Value as JObject;
                        changeSet = TChangeSet.BuildChangeSet(changeSetObj);
                    }
                    continue;
                }
                if (item.Value == default(JToken))
                {
                    pairs.Add(item.Name, item.Value);
                }
                else
                {
                    if (item.Value.Type == JTokenType.Object)
                    {
                        pairs.Add(item.Name, BuildRecord(item.Value as JObject));
                    }
                    else if (item.Value.Type == JTokenType.Array)
                    {
                        List<TRecord> records = new List<TRecord>();
                        foreach (var aItem in item.Value as JArray)
                        {
                            records.Add(BuildRecord(aItem as JObject));
                        }
                        pairs.Add(item.Name, records);
                    }
                    else
                    {
                        if (item.Value.Type == JTokenType.Boolean)
                        {
                            pairs.Add(item.Name, Convert.ToBoolean(item.Value) ? 1 : 0);
                        }
                        else if (item.Value.Type == JTokenType.String)
                        {
                            pairs.Add(item.Name, Convert.ToString(item.Value));
                        }
                        else if (item.Value.Type == JTokenType.Integer)
                        {
                            pairs.Add(item.Name, Convert.ToInt64(item.Value == null ? 0 : item.Value));
                        }
                        else if (item.Value.Type == JTokenType.Date)
                        {
                            pairs.Add(item.Name, Convert.ToDateTime(item.Value));
                        }
                        else if (item.Value.Type == JTokenType.Float)
                        {
                            pairs.Add(item.Name, Convert.ToDouble(item.Value == null ? 0 : item.Value));
                        }
                        else
                            pairs.Add(item.Name, item.Value as Object);
                    }
                }
            }

            TRecord record = new TRecord(pairs);
            if (changeSet != default)
            {
                record.DeleteMember(ChangeSetPropertyName);
                record.ChangeSet = changeSet;
            }
            return record;
        }

        public static JObject ToJson(TRecord record)
        {
            var jObj = JObject.FromObject(record);
            if (record.ChangeSet != null)
            {
                var changeSetObj = TChangeSet.ToJson(record.ChangeSet);
                if (changeSetObj != null)
                    jObj[ChangeSetPropertyName] = changeSetObj;
            }
            foreach (var prop in record.GetProperties())
            {
                if (record[prop] is TRecord childRecord)
                {
                    jObj[prop] = ToJson(childRecord);
                }
                else if (record[prop] is List<TRecord> childRecords)
                {
                    var arr = new JArray();
                    foreach (var childP in childRecords)
                    {
                        arr.Add(ToJson(childP));
                    }
                    jObj[prop] = arr;
                }
            }
            return jObj;
        }

        public object GetValue(string propertyName)
        {
            if (_properties != null && !string.IsNullOrWhiteSpace(propertyName) && _properties.ContainsKey(propertyName))
            {
                return _properties[propertyName];
            }
            return null;
        }

        public bool IsContainProperty(string propertyName)
        {
            if (_properties != null && !string.IsNullOrWhiteSpace(propertyName) && _properties.ContainsKey(propertyName))
            {
                return true;
            }
            return false;
        }

        public void SetValue(string propertyName, object dataValue)
        {
            UpdateChangeSet(propertyName, dataValue);
            if (_properties.ContainsKey(propertyName))
                _properties[propertyName] = dataValue;
            else
                _properties.Add(propertyName, dataValue);
        }

        public int DeleteMember(string propertyName)
        {
            if (_properties.ContainsKey(propertyName))
            {
                _properties.Remove(propertyName);
                return 1;
            }
            else
            {
                return 0;
            }
        }

        /// <summary>
        /// 获取当前Record的Key值集合
        /// </summary>
        /// <returns></returns>
        public List<string> GetProperties()
        {
            return _properties?.Keys.ToList();
        }

        /// <summary>
        /// 清理TRecord：1、标记为Delete的
        /// 注意当Record有循环引用是会出现堆栈溢出场景，根据CDO最深为3层的特性，加入深度条件判断
        /// </summary>
        /// <returns></returns>
        public void CleanOut(int depth = 0)
        {
            if (depth > RecursiveDepth) return;
            var keys = this.GetProperties();
            foreach (var key in keys)
            {
                var data = this[key];
                if (data == null) continue;
                if (data is TRecord trecord)
                {
                    if (trecord.ChangeType == TRecordChangeType.Delete || (trecord.ChangeSet != null && trecord.ChangeSet.ChangeType == TRecordChangeType.Delete))
                        this.DeleteMember(key);
                    else if (trecord.ChangeType == TRecordChangeType.Update || (trecord.ChangeSet != null && trecord.ChangeSet.ChangeType == TRecordChangeType.Update))
                        trecord.CleanOut(depth + 1);
                }
                else if (data is List<TRecord> trecords && trecords.Count > 0)
                {
                    List<TRecord> needDropRecords = new List<TRecord>();
                    foreach (var record in trecords)
                    {
                        if (record == null) continue;
                        if (record.ChangeType == TRecordChangeType.Delete || (record.ChangeSet != null && record.ChangeSet.ChangeType == TRecordChangeType.Delete))
                            needDropRecords.Add(record);
                        else if (record.ChangeType == TRecordChangeType.Update || (record.ChangeSet != null && record.ChangeSet.ChangeType == TRecordChangeType.Update))
                            record.CleanOut(depth + 1);
                    }
                    if (needDropRecords.Count > 0)
                    {
                        foreach (var item in needDropRecords)
                        {
                            trecords.Remove(item);
                        }
                    }
                }
            }
        }

        private bool CheckChildHasChange(TRecord curRecord, int depth = 0)
        {
            if (depth > RecursiveDepth) return false;
            var keys = curRecord.GetProperties();
            bool isChanged = false;
            foreach (var key in keys)
            {
                var data = curRecord[key];
                if (data == null) continue;
                if (data is TRecord trecord)
                {
                    if (trecord._changeType == TRecordChangeType.UnChanged)
                    {
                        isChanged = CheckChildHasChange(trecord, depth + 1);
                        if (isChanged)
                        {
                            trecord.ChangeType = TRecordChangeType.Update;
                            break;
                        }
                    }
                    else
                    {
                        curRecord.ChangeType = TRecordChangeType.Update;
                        isChanged = true;
                        break;
                    }
                }
                else if (data is List<TRecord> trecords && trecords.Count > 0)
                {
                    foreach (var record in trecords)
                    {
                        if (record == null) continue;
                        if (record._changeType == TRecordChangeType.UnChanged)
                        {
                            isChanged = CheckChildHasChange(record, depth + 1);
                            if (isChanged)
                            {
                                record.ChangeType = TRecordChangeType.Update;
                                break;
                            }
                        }
                        else
                        {
                            curRecord.ChangeType = TRecordChangeType.Update;
                            isChanged = true;
                            break;
                        }
                    }
                    if (isChanged)
                        break;
                }
            }
            return isChanged;
        }

        /// <summary>
        /// TRecord Clone方法，统一数据访问服务会用到
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {
            Dictionary<string, object> newObj = new Dictionary<string, object>();
            foreach (var item in this._properties)
            {
                if (item.Value is TRecord record)
                {
                    newObj[item.Key] = record.Clone() as TRecord;
                }
                else if (item.Value is List<TRecord> listRecord)
                {
                    List<TRecord> records = new List<TRecord>();
                    foreach (var re in listRecord)
                    {
                        records.Add(re.Clone() as TRecord);
                    }
                    newObj[item.Key] = records;
                }
                else
                {
                    newObj[item.Key] = item.Value;
                }
            }

            if (!string.IsNullOrEmpty(PKColumnName) && newObj.ContainsKey(PKColumnName))
            {
                var obj = newObj[PKColumnName];
                if (obj is string)
                {
                    newObj[PKColumnName] = Guid.NewGuid().ToString();
                }
                else if (obj is int)
                {
                    newObj[PKColumnName] = DateTime.Now.GetHashCode();
                }
                else if (obj is long)
                {
                    newObj[PKColumnName] = DateTime.Now.GetHashCode();
                }
                else
                    newObj.Remove(PKColumnName);
            }

            return new TRecord(newObj) { PKColumnName = this.PKColumnName };
        }

        /// <summary>
        /// TRecord Clone方法，不会丢失主键，统一数据访问服务会用到
        /// </summary>
        /// <returns></returns>
        public object CloneWithoutChangePkValue()
        {
            Dictionary<string, object> newObj = new Dictionary<string, object>();
            foreach (var item in this._properties)
            {
                if (item.Value is TRecord record)
                {
                    newObj[item.Key] = record.CloneWithoutChangePkValue() as TRecord;
                }
                else if (item.Value is List<TRecord> listRecord)
                {
                    List<TRecord> records = new List<TRecord>();
                    foreach (var re in listRecord)
                    {
                        records.Add(re.CloneWithoutChangePkValue() as TRecord);
                    }
                    newObj[item.Key] = records;
                }
                else
                {
                    newObj[item.Key] = item.Value;
                }
            }

            return new TRecord(newObj) { PKColumnName = this.PKColumnName };
        }

        #endregion

        #region 数据变更相关
        public TChangeSet ChangeSet { get; set; }
        /// <summary>
        /// 数据变更时更新变更集
        /// </summary>
        /// <param name="name"></param>
        /// <param name="newValue"></param>
        private void UpdateChangeSet(string name, object newValue)
        {
            if (ChangeSet == null)
            {
                ChangeSet = new TChangeSet();
                ChangeSet.ChangeType = TRecordChangeType.Update;
            }
            if (this.ChangeType == TRecordChangeType.UnChanged)
                this.ChangeType = TRecordChangeType.Update;
            var item = new TChangeSetItem();
            item.BusinessType = GetBusinessType(name);
            item.OldValue = GetValue(name);
            item.NewValue = newValue;
            item.ColumnCode = name;
            this.ChangeSet.AddChangeSetItem(item);
        }

        private BusinessType GetBusinessType(string name)
        {
            if (this.StateColumnNames != null && this.StateColumnNames.Exists(x => x.Equals(name, StringComparison.OrdinalIgnoreCase)))
            {
                return BusinessType.State;
            }
            if (!string.IsNullOrEmpty(this.PKColumnName) && this.PKColumnName.Equals(name, StringComparison.OrdinalIgnoreCase))
            {
                return BusinessType.PK;
            }
            if (this.SDOColumnNames != null && this.SDOColumnNames.Exists(x => x.Equals(name, StringComparison.OrdinalIgnoreCase)))
            {
                return BusinessType.SDO;
            }
            return BusinessType.Normal;
        }


        private TRecordChangeType _changeType = TRecordChangeType.UnChanged;
        public TRecordChangeType ChangeType
        {
            get
            {
                if (_changeType == TRecordChangeType.UnChanged)
                {
                    var hasChanged = CheckChildHasChange(this);
                    if (hasChanged)
                        _changeType = TRecordChangeType.Update;
                }
                return _changeType;
            }
            set { _changeType = value; }
        }

        /// <summary>
        /// 获取数据变更日志
        /// </summary>
        /// <returns></returns>
        public DataChangeLog GetAllDataChangeLog()
        {
            if (this.ChangeType == TRecordChangeType.UnChanged)
                return null;
            var log = new DataChangeLog();
            log.BillID = PKColumnValue;
            log.Action = this.ChangeType == TRecordChangeType.Add ? "Create" : (this.ChangeType == TRecordChangeType.Delete ? "Delete" : "Update");
            log.Datas = new List<DataChangeItem>();
            UpdateDataChangeLogMainInfo(log, this.ChangeSet);
            if (!string.IsNullOrEmpty(TimestampColumnName))
            {
                var timestr = this.GetValue(TimestampColumnName)?.ToString();
                if (!string.IsNullOrEmpty(timestr))
                {
                    if (DateTime.TryParse(timestr, out DateTime dt))
                    {
                        log.CreateTime = dt;
                        log.Ext20 = Convert.ToString(dt);
                    }
                    else
                    {
                        try
                        {
                            //fireevent改单据状态时，时间戳字段值为json
                            if (JObject.Parse(timestr).TryGetValue("Data", out JToken data)
                                && DateTime.TryParse(data.ToString(), out DateTime dt2))
                            {

                                log.CreateTime = dt2;
                                log.Ext20 = Convert.ToString(dt2);
                            }
                            else
                            {
                                LogTrace($"处理时间戳失败, timestr:{timestr}");
                            }
                        }
                        catch (Exception e)
                        {
                            LogTrace($" timestr:{timestr}", e);
                        }
                    }
                }
            }
            if (log.CreateTime.Year == 1)
            {
                log.CreateTime = DateTime.Now;
                log.Ext20 = Convert.ToString(DateTime.Now);
            }
            if (this.ChangeType == TRecordChangeType.Add || this.ChangeType == TRecordChangeType.Update)
            {
                //如果自身有字段的变更，先进行本身的变更处理
                if (this.ChangeSet.Count > 0)
                {
                    //当前节点本身的属性变更，直接记录大数据日志
                    var changeItems = this.ChangeSet.ChangeSetItems.OrderBy(x => x.SortOrder).ToList();
                    foreach (var x in changeItems)
                    {
                        UpdateDataChangeLogStateInfo(log, x);
                        if (x.BusinessType == BusinessType.State)
                        {
                            log.Datas.Add(new DataChangeItem()
                            {
                                ID = Guid.NewGuid().ToString(),
                                DataColumn = GetColumnNameByCode(x.ColumnCode),
                                OldData = Convert.ToString(x.InitDisplayName),
                                NewData = Convert.ToString(x.NewDisplayValue)
                            });
                        }
                        else
                        {
                            string oldData = GetChangeSetItemVal(x.InitValue, x.DataType);  //使用InitValue记录
                            string newData = GetChangeSetItemVal(x.NewValue, x.DataType);
                            log.Datas.Add(new DataChangeItem()
                            {
                                ID = Guid.NewGuid().ToString(),
                                DataColumn = GetColumnNameByCode(x.ColumnCode),
                                OldData = oldData,
                                NewData = newData
                            });
                        }
                    }
                }
                //检查子集节点是否有变化
                BuildChangeSetRecursive(log, null, this, 1, true);
            }
            else if (this.ChangeType == TRecordChangeType.Delete)
            {
                log.Datas.Add(new DataChangeItem()
                {
                    ID = Guid.NewGuid().ToString(),
                    OldData = JsonConvert.SerializeObject(this),
                    NewData = "",
                });
            }
            return log;
        }

        private void LogTrace(string msg = null ,Exception e = null)
        {
            TrackLog traceLog = new TrackLog
            {
                FuncID = "changeLog",
                DataKey = PKColumnValue,
                ModuleCode = "FASDAC",
                Ext1 = PKColumnValue,
                Context = msg ?? string.Empty,
            };
            if (e != null)
                traceLog.Context += $"处理单据时间戳失败，msg:{e?.Message}, e:{e}";
            traceLog.Ext10 = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
            new LogLocalClient().SendLogs(new ILog[] { traceLog });
        }

        private string GetColumnNameByCode(string code)
        {
            if (AllColumnInfos != null && AllColumnInfos.ContainsKey(code))
            {
                return AllColumnInfos[code];
            }
            return code;
        }
        private TChangeSet BuildChangeSetRecursive(DataChangeLog log, TChangeSet parentChangeSet, TRecord record, int depth, bool isRootNode = false)
        {
            if (depth > RecursiveDepth)
                return null;
            if (record == null)
                return null;
            UpdateDataChangeLogMainInfo(log, record.ChangeSet);

            //当前节点如果不是根节点，本身的属性变更，直接记录大数据日志
            if (!isRootNode && record.ChangeSet != null && record.ChangeSet.ChangeSetItems != null)
            {
                record.ChangeSet.ChangeSetItems.OrderBy(x => x.SortOrder).ToList().ForEach(x =>
                {
                    parentChangeSet.AddChangeSetItem(x.Clone());
                });
            }
            //查询复杂对象
            var keys = record.GetProperties();
            foreach (var key in keys)
            {
                var data = this[key];
                if (data == null)
                    continue;
                if (data is TRecord trecord)
                {
                    if (!isRootNode)
                        UpdateChangeSetRecursiveFromRecord(log, parentChangeSet, key, trecord, depth);
                    else
                    {
                        if (parentChangeSet == null)
                            parentChangeSet = new TChangeSet();
                        log.Datas.Add(new DataChangeItem()
                        {
                            ID = Guid.NewGuid().ToString(),
                            DataColumn = key,
                            NewData = JsonConvert.SerializeObject(BuildChangeSetRecursive(log, parentChangeSet, trecord, depth + 1))
                        });
                    }
                }
                else if (data is List<TRecord> trecords && trecords.Count > 0)
                {
                    var list = new List<TChangeSet>();
                    var index = 0;
                    foreach (var re in trecords)
                    {
                        if (re.ChangeType == TRecordChangeType.UnChanged)
                            continue;
                        var cs = new TChangeSet();
                        UpdateChangeSetRecursiveFromRecord(log, cs, key + index, re, depth);
                        list.Add(cs);
                        index++;
                    }
                    if (list.Count <= 0)
                        continue;
                    if (!isRootNode)
                    {
                        parentChangeSet.AddChangeSetItem(new TChangeSetItem()
                        {
                            ColumnCode = key,
                            NewValue = list
                        });
                    }
                    else
                    {
                        if (parentChangeSet == null)
                            parentChangeSet = new TChangeSet();
                        log.Datas.Add(new DataChangeItem()
                        {
                            ID = Guid.NewGuid().ToString(),
                            DataColumn = key,
                            NewData = JsonConvert.SerializeObject(list)
                        });
                    }
                }
            }
            return parentChangeSet;
        }

        private void UpdateChangeSetRecursiveFromRecord(DataChangeLog log, TChangeSet parentChangeSet, string key, TRecord trecord, int depth)
        {
            if (trecord.ChangeType == TRecordChangeType.UnChanged)
                return;
            if (trecord.ChangeType == TRecordChangeType.Update)
            {
                var tChangeSet = new TChangeSet()
                {
                    ChangeType = TRecordChangeType.Update
                };
                parentChangeSet.AddChangeSetItem(new TChangeSetItem()
                {
                    ColumnCode = key,
                    NewValue = BuildChangeSetRecursive(log, tChangeSet, trecord, depth + 1),
                });
            }
            else if (trecord.ChangeType == TRecordChangeType.Add)
            {
                parentChangeSet.AddChangeSetItem(new TChangeSetItem()
                {
                    ColumnCode = key,
                    NewValue = new TChangeSet(new TChangeSetItem() { NewValue = trecord })
                    {
                        ChangeType = TRecordChangeType.Add,
                    }
                });
            }
            else if (trecord.ChangeType == TRecordChangeType.Delete)
            {
                parentChangeSet.AddChangeSetItem(new TChangeSetItem()
                {
                    ColumnCode = key,
                    OldValue = new TChangeSet()
                    {
                        PKColumnValue = trecord.PKColumnValue,
                        ChangeType = TRecordChangeType.Delete
                    }
                });
            }
        }

        private void UpdateDataChangeLogMainInfo(DataChangeLog log, TChangeSet changeSet)
        {
            if (changeSet == null)
                return;
            if (string.IsNullOrEmpty(log.UserID))
                log.UserID = changeSet.Modifier;
            //解决CDO变更日志的修改时间问题
            if (log.CreateTime.Year == 1 && changeSet.ModifyTime.Year != 1)
                log.CreateTime = changeSet.ModifyTime;
            if (string.IsNullOrEmpty(log.Ext15))
                log.Ext15 = changeSet.CDOID;
            if (string.IsNullOrEmpty(log.Ext16))
                log.Ext16 = changeSet.TenantID;
            if (string.IsNullOrEmpty(log.Ext17))
                log.Ext17 = changeSet.CDOCode;
            if (string.IsNullOrEmpty(log.Ext18))
                log.Ext18 = changeSet.CDOName;
            if (string.IsNullOrEmpty(log.Ext19))
                log.Ext19 = changeSet.ModifierName;
        }

        private void UpdateDataChangeLogStateInfo(DataChangeLog log, TChangeSetItem item)
        {
            if (item == null)
                return;
            if (item.BusinessType == BusinessType.State)
            {
                log.Ext11 = Convert.ToString(item.InitValue);
                log.Ext12 = Convert.ToString(item.InitDisplayName);
                log.Ext13 = Convert.ToString(item.NewValue);
                log.Ext14 = Convert.ToString(item.NewDisplayValue);

            }
        }


        #endregion

        #region Private Funcs
        /// <summary>
        /// 判断该Record是否为时间戳Record
        /// </summary>
        /// <param name="timeVal"></param>
        /// <returns></returns>
        public bool IsTimestampRecord(out string timeVal)
        {
            timeVal = null;
            if (this._properties.Count == 3
                    && this._properties.ContainsKey("Data")
                    && this._properties.ContainsKey("Value")
                    && this._properties.ContainsKey("Format"))
            {
                string dataVal = this._properties["Data"]?.ToString();
                if (!string.IsNullOrEmpty(dataVal) && DateTime.TryParse(dataVal, out DateTime timestamp))
                {
                    timeVal = dataVal;
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 将变更集项的Value值处理为String类型
        /// </summary>
        /// <param name="value"></param>
        /// <param name="dataType"></param>
        /// <returns></returns>
        public string GetChangeSetItemVal(object value, DataType dataType)
        {
            string oldData;
            //修复BFO20220928180439378 变更日志时间戳旧值显示不对的问题
            if (value is TRecord record)
            {
                if (record.IsTimestampRecord(out string timeVal))
                {
                    oldData = timeVal;
                }
                else
                {
                    oldData = ToJson(record).ToString();
                }
            }
            else
            {
                switch (dataType)
                {
                    case DataType.DateTime:
                        if (value is DateTime dt)
                            //todo:后续可以在ChangeSetItem中增加ext配置，存储指定format格式
                            oldData = dt.ToString("yyy-MM-dd HH:mm:ss.fff");
                        else
                            oldData = Convert.ToString(value);
                        break;
                    case DataType.Date:
                        if (value is DateTime dt2)
                            oldData = dt2.ToString("yyy-MM-dd");
                        else
                            oldData = Convert.ToString(value);
                        break;
                    case DataType.Time:
                        if (value is DateTime dt3)
                            oldData = dt3.ToString("HH:mm:ss.fff");
                        else if (value is TimeSpan ts)
                            oldData = ts.ToString(@"hh\:mm"); //@"hh\:mm\:ss\.fff" 时分秒毫秒
                        else
                            oldData = Convert.ToString(value);
                        break;
                    default:
                        oldData = Convert.ToString(value);
                        break;
                }
            }
            return oldData;
        }
        #endregion
    }

    public enum TRecordChangeType
    {
        UnChanged,
        Add,
        Delete,
        Update,
    }
}